Claimed resources synchronization - #155
Conversation
c2f3eef to
12dcb1a
Compare
| ClaimRejected AcceptablePermissionClaimState = "Rejected" | ||
| ) | ||
|
|
||
| // PermissionClaim identifies an object by GR and identity hash. |
There was a problem hiding this comment.
| // PermissionClaim identifies an object by GR and identity hash. | |
| // PermissionClaim selects objects of a GVR that the service provider ... |
| type PermissionClaim struct { | ||
| GroupResource `json:","` | ||
|
|
||
| Version string `json:"version"` |
There was a problem hiding this comment.
valdiation: required and non-empty
There was a problem hiding this comment.
can't we simply reference GroupVersionResource, why is GroupResource and Version separate?
There was a problem hiding this comment.
non-empty need validation, aka minlength
| Version string `json:"version"` | ||
|
|
||
| // selector selects which resources are affected by this claim. | ||
| Selector ResourceSelector `json:"selector,omitempty"` |
There was a problem hiding this comment.
omitempty has no effect on structs. We could turn it into a pointer if that's prettier.
| Selector ResourceSelector `json:"selector,omitempty"` | ||
|
|
||
| // required indicates whether the APIServiceBinding will work if this claim is not accepted. | ||
| Required bool `json:"required"` |
There was a problem hiding this comment.
I think it's better to make it opt in. It would make people think about whether it is actually needed. Since required claims cannot be denied, we should guide users to use required as little as needed by making the default false.
There was a problem hiding this comment.
Since required claims cannot be denied
we should make this fact part of the API doc.
| // Global claims global resources for the given group/resource. | ||
| // This is mutually exclusive with resourceSelector. | ||
| // +optional | ||
| Global bool `json:"global,omitempty"` |
There was a problem hiding this comment.
I mostly added it to support cluster-scoped resources. However I'm not sure if that's actually something we want to enable. The problem being that given any claim but single-name claims, the user could have access to cluster-scoped objects of other bind consumers.
There was a problem hiding this comment.
I am not convinced we should not allow cluster scoped resources. If we do, we lift the concept of a namespace to be a tenancy boundary which might actually not be desired. If used i.e. with kcp, claiming cluster-scoped resources is actually fine, as tenancy boundaries are defined one level up, at the workspace level.
| // | ||
| // create determines whether the kube-bind konnector will sync matching objects from the | ||
| // provider side down to the consumer cluster. | ||
| Create CreateOptions `json:"create"` |
There was a problem hiding this comment.
this must be a pointer. There is no need that a service provider claims creation.
|
|
||
| // adopt set to true means that objects created by the consumer are adopted by the provider. | ||
| // i.e. the provider will become the owner. | ||
| Adopt bool `json:"adopt"` |
|
|
||
| // update lists a number of claimed permissions for the provider. | ||
| // "field" and "preserving" are mutually exclusive. | ||
| Update UpdateOptions `json:"update"` |
| // providerOverrides will make the provider override any object that might already exist | ||
| // in the consumer cluster if it has the same namespaced name as a resource created by the | ||
| // provider. |
There was a problem hiding this comment.
| // providerOverrides will make the provider override any object that might already exist | |
| // in the consumer cluster if it has the same namespaced name as a resource created by the | |
| // provider. | |
| // providerOverrides will make the provider override any object that might already exist | |
| // in the consumer cluster if it has the same namespaced name as a resource created by the | |
| // provider, but is not the result of syncing. |
| type CreateOptions struct { | ||
| // donate set to true means that a newly created object by the provider is immediately owned by hte consumer. | ||
| // If false, the object stays in ownership of the provider | ||
| Donate bool `json:"donate"` |
| // only for owner provider | ||
| // When recreateWhenConsumerSideDeleted is true the provider will recreate the object | ||
| // in case the object is missing on the consumer side. Even if the consumer mistakenly or intentionally |
There was a problem hiding this comment.
| // only for owner provider | |
| // When recreateWhenConsumerSideDeleted is true the provider will recreate the object | |
| // in case the object is missing on the consumer side. Even if the consumer mistakenly or intentionally | |
| // recreateWhenConsumerSideDeleted set to true means the provider will recreate the object | |
| // in case the object is missing on the consumer side. Even if the consumer mistakenly or intentionally. |
| // fields are the fields owned by the owner of the claim. If the owner sets values of those | ||
| // fields, they will be synced to the other participant. | ||
| // Mutually exclusive with preservings. |
There was a problem hiding this comment.
would not use the word "owner" here. The claim owner is always the provider, i.e. claims apply to the provider only.
| // fields are the fields owned by the owner of the claim. If the owner sets values of those | |
| // fields, they will be synced to the other participant. | |
| // Mutually exclusive with preservings. | |
| // fields are a list of JSON Paths describing which parts of an object the provider wants to control in case | |
| // the object is owned by the consumer as a whole. This field cannot be set if provider owned objects | |
| // are selected in this claim, and must be non-empty otherwise. |
There was a problem hiding this comment.
The owner term was taken from the selector. It describes the binding party that, if not overridden, will be the side to create and delete the resource.
My interpretation of "fields" and "preserving" was that:
"fields" is a whitelist for synchronization, meaning everything in "fields" uses the owner of the claimed resource (owner can be consumer or provider) as the source of truth. Everything not in "fields" uses the other side (if the owner is consumer it used provider) as the source of truth.
"excludes" is a blacklist, meaning that fields that are referenced in "excludes" do not use the owner of the resource as the source of truth, and everything not in "excludes" uses the owner as the source of truth.
There was a problem hiding this comment.
These claims are always from the point of view of the provider. There are two cases:
- the provider is owner =>
preservingdefines exceptions that the provider (here the owner) does not claim to own - the consumer is the owner =>
fieldsdefines exceptions that the provider claims to own despite the consumer being the owner.
In other words: the subject of the claim is always the provider. There is no inversion of the subject when the owner changes.
There was a problem hiding this comment.
I think there are two points to be clarified here.
- Inversion of subject. I understand that we don't want to invert it.
- The blacklist/whitelist part.
quoting parts of an example prompt:
read spec.nodeName and update status
How would this be expressed?
| // Preservings are the fields that are preserved by the konnector during synchronization. | ||
| // The owner is not able to set those fields. If the owner changes the value of these fields, | ||
| // their change will be overwritten. |
There was a problem hiding this comment.
| // Preservings are the fields that are preserved by the konnector during synchronization. | |
| // The owner is not able to set those fields. If the owner changes the value of these fields, | |
| // their change will be overwritten. | |
| // preserving is a list of JSON Paths describing fields that should be preserved while updating with the | |
| // object state on the provider side. This field cannot be set if consumer owned objects are selected in | |
| // this claim. |
| Owner Owner `json:"owner"` | ||
|
|
||
| // | ||
| // WARNING: If adding new fields, add them to the XValidation check! |
There was a problem hiding this comment.
Probably not. I would argue it makes sense to make the whole permissionClaim immutable though.
If we make it mutable, and simply take over the changes to the consumer, the provider could change the permission after it has been accepted, leading to a compromised consumer.
| type Owner string | ||
|
|
||
| const Provider Owner = "Provider" | ||
| const Consumer Owner = "Consumer" |
| // +listType=map | ||
| // +listMapKey=group | ||
| // +listMapKey=resource | ||
| // +kubebuilder:validation:MaxItems=2 |
| // +optional | ||
| // +listType=map | ||
| // +listMapKey=group | ||
| // +listMapKey=resource |
There was a problem hiding this comment.
don't think we are able to define a key for this list. This is rather a set.
| // a sensible default is chosen by the service provider. | ||
| Versions []string `json:"versions,omitempty"` | ||
| Versions []string `json:"versions,omitempty"` | ||
| PermissionClaims []PermissionClaim `json:"permissionClaims,omitempty"` |
| @@ -0,0 +1,69 @@ | |||
| /* | |||
| Copyright 2022 The Kube Bind Authors. | |||
| // +kubebuilder:subresource:status | ||
| // +kubebuilder:printcolumn:name="Established",type="string",JSONPath=`.status.conditions[?(@.type=="Established")].status`,priority=5 | ||
| // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=`.metadata.creationTimestamp`,priority=0 | ||
| type APIServiceExportTemplate struct { |
There was a problem hiding this comment.
this is interesting, especially the selector. Don't think we have explored this side yet. Worth a discussion in the gdoc? My gut feeling is that this is really part of a demo backend, but could have different shape depending on context. For example the claims could differ depending on group membership or on input the UI.
There was a problem hiding this comment.
There is a section on the gdoc called "Associating permissionClaims with Resources" which touches on this. I agree with the idea of making this part of the demo backend. Should I move the API to the example backend?
There was a problem hiding this comment.
i am actually not understanding why this is needed.
There was a problem hiding this comment.
Suggestion: a different API group example-backend.kube-bind.io/v1alpha1
| type: object | ||
| x-kubernetes-validations: | ||
| - message: donate and adopt are mutually exclusive | ||
| rule: '!(self.create.donate && self.adopt)' |
There was a problem hiding this comment.
This validation broke the e2e tests, so i temporarily disabled it.
|
|
||
| type OnConflictOptions struct { | ||
| // providerOverrides will make the provider override any object that might already exist | ||
| // in the consumer cluster if it has the same namespaced name as a resource created by the |
There was a problem hiding this comment.
why the restriction to namespaced name ?
Add new type APIServiceExportTemplate which provides a template for new APIServiceExports the type associates the claimed resources with the CRD Add claimed resource information to APIBinding and APIExport Current Limitation: Validation is commented out because the e2e testing infrastructure rejected the validation for being too computationally expensive
CRDs to be displayed are now based on APIServiceExportTemplates and not on a label Claimed resources are added to the APIServiceExport Claimed resources are sent to the cli plugin
Add prompt for permission claim requests Add permissionclaims including accepted state to APIBinding
Add a new controller and reconciler for downsyncing claimed resources
|
|
||
| // CreateFromFS creates the given CRDs using the target client from the | ||
| // provided filesystem and waits for it to become established. This call is blocking. | ||
| func CreateFromFS(ctx context.Context, client apiextensionsv1client.CustomResourceDefinitionInterface, fs embed.FS, grs ...metav1.GroupResource) error { |
There was a problem hiding this comment.
remind me where this code comes from and why we need it? Is it from kcp? Looks familar.
There was a problem hiding this comment.
|
|
||
| const ( | ||
| // GroupName is the group name used in this package | ||
| GroupName = "example.com" |
There was a problem hiding this comment.
example-backend.kube-bind.io would be better.
| deleteServiceExportRequest: func(ctx context.Context, ns, name string) error { | ||
| return bindClient.KubeBindV1alpha1().APIServiceExportRequests(ns).Delete(ctx, name, metav1.DeleteOptions{}) | ||
| }, | ||
| crds: exporttemplate.NewCatalogue(config), |
There was a problem hiding this comment.
code follows American spelling, i.e. catalog
| templates "github.com/kube-bind/kube-bind/contrib/example-backend/client/clientset/versioned" | ||
| ) | ||
|
|
||
| type Index struct { |
There was a problem hiding this comment.
not sure I can follow this construct. What is it?
There was a problem hiding this comment.
The backend needs to know which CRD get which claimedresources. I stored them in APIServiceExportTemplates. Index lets the backend get all exported CRDs (those that are referenced by a APIServiceExportTemplate), and to look up the template for a CRD so it can attach the claimed resources
| OnConflict *OnConflictOptions `json:"onConflict,omitempty"` | ||
|
|
||
| // update lists which updates to objects on the consumer cluster are claimed. | ||
| // By default, the whole object is synced, but metadata is not. |
There was a problem hiding this comment.
| // By default, the whole object is synced, but metadata is not. | |
| // By default, the whole object is continously synced, but metadata is not after creation. |
Is this correct?
There was a problem hiding this comment.
What would be the reason for syncing metadata on creation, but not afterwards? IIRC we wanted to not sync metadata by default including creation
| // Even if the consumer mistakenly or intentionally | ||
| // deletes the object, the provider will recreate it. If the field is set as false, | ||
| // the provider will not recreate the object in case the object is deleted on the consumer cluster. |
There was a problem hiding this comment.
These are redundant? Might have been old text in our gdoc. Or is there a detail I am missing?
There was a problem hiding this comment.
yes, let's remove them from both places
| type UpdateOptions struct { | ||
| // fields are a list of JSON Paths describing which parts of an object the provider wants to control. | ||
| // | ||
| // This field is ignored if the owner in the claim selector is set to "Provider". |
There was a problem hiding this comment.
this wording is strange. The selector selects objects, either consumer or provider owned. This here is about the object owner, not about the selector.
| // Namespaces matches against the metadata.namespace field. A value of "*" matches namespaced objects across all namespaces. | ||
| // If a resources namespace matches any value in namespaces, the resource namespace is considered matching. | ||
| // If the claim is for a cluster-scoped resource, namespaces has to explicitly be set to an empty array to prevent defaulting to "*". | ||
| // If the "names" field is unset, all objects of the group/resource within the listed namespaces (or cluster) will be claimed. |
There was a problem hiding this comment.
names cannot be unset. It's defaulted to *.
There was a problem hiding this comment.
What should we do for cluster-scoped objects?
| // +optional | ||
| Namespaces []string `json:"namespaces,omitempty"` | ||
|
|
||
| // labelSelectors is a list of label selectors matching selected resources. label selectors follow the same rules as kubernetes label selectors, |
There was a problem hiding this comment.
Disjunction for items of the array, conjunction for key-value pairs in the map
| // TODO | ||
| if onlyFirstServingVersion && !crdVersion.Storage { | ||
| continue | ||
| } |
There was a problem hiding this comment.
would expect this to be a parameter to CRDToServiceExport and controlled through higher level means.
There was a problem hiding this comment.
this change can be removed. The intentions were the same as in #181
| @@ -1,5 +1,5 @@ | |||
| /* | |||
| Copyright 2022 The Kube Bind Authors. | |||
| Copyright 2023 The Kube Bind Authors. | |||
There was a problem hiding this comment.
we don't update these for existing files.
| @@ -0,0 +1,824 @@ | |||
| /* | |||
There was a problem hiding this comment.
would love to see us merge servicebindings.go and this test, plus the API types first, to get this in step by step instead of 6000 lines at once.
| } | ||
|
|
||
| func (opts *BindOptions) Cleanup() { | ||
| os.Remove(opts.outFile.Name()) |
There was a problem hiding this comment.
this is strange to be attached to options. Options are supposed to be just a data structure of flag values.
Rename api group for backend configuration Custom Resources from example.com to example-backend.kube-bind.io
Co-authored-by: Dr. Stefan Schimanski <stefan.schimanski@gmail.com>
Co-authored-by: Dr. Stefan Schimanski <stefan.schimanski@gmail.com>
84ddc19 to
0d12467
Compare
| kubebindv1alpha1 "github.com/kube-bind/kube-bind/pkg/apis/kubebind/v1alpha1" | ||
| ) | ||
|
|
||
| const annotation = "kube-bind.io/resource-owner" |
There was a problem hiding this comment.
kube-bind.io/owner would be better
A missing nil check for the Selector led to controller crashes
|
implemented as part of #304 |
Discussion doc: https://docs.google.com/document/d/1Mfk2wLIURqAwSjoTimuZlL83BhslTiqV/edit#
Scope
The prototype only implements one basic use case: downsync of all resources of a type.
This includes association of the resources to the api export in the backend, synchronization by the konnector, and basic prompts for the claim in the bind cli
Current limitations